package com.flyqiu.flow.core.impl;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.flyqiu.common.bean.FlyQiuBeanManage;
import com.flyqiu.common.tools.FlyQiuCollectionTools;
import com.flyqiu.common.tools.StringTools;
import com.flyqiu.common.transaction.FlyQiuTransactionManage;
import com.flyqiu.common.user.FlyQiuUser;
import com.flyqiu.common.user.FlyQiuUserTools;
import com.flyqiu.flow.event.ActInstEvent;
import com.flyqiu.flow.event.ProcInstEvent;
import com.flyqiu.flow.event.UserTaskEvent;
import com.flyqiu.flow.model.business.BaseFlyBusiness;
import com.flyqiu.flow.api.cache.BpmnModelCache;
import com.flyqiu.flow.api.context.FlyQiuFlowGlobalContext;
import com.flyqiu.flow.api.context.ProcInstContext;
import com.flyqiu.flow.api.service.FlowRunningService;
import com.flyqiu.flow.api.service.IdentityLinkService;
import com.flyqiu.flow.api.service.base.FlowCommentService;
import com.flyqiu.flow.api.service.base.FlowVariableService;
import com.flyqiu.flow.api.service.ru.FlowRuVariableService;
import com.flyqiu.flow.api.std.ElementCompleteHandler;
import com.flyqiu.flow.bpmn.element.ProcessModel;

import com.flyqiu.flow.common.enums.ProcDefStatus;
import com.flyqiu.flow.common.enums.ProcInstStatus;
import com.flyqiu.flow.common.std.IProIcActivityMove;
import com.flyqiu.flow.core.DefaultProcInstContext;
import com.flyqiu.flow.core.fliter.DefaultBpmnPathFilter;
import com.flyqiu.flow.core.handler.ProcessHandler;
import com.flyqiu.flow.core.tools.DefaultProcMove;
import com.flyqiu.flow.core.var.VariableScope;
import com.flyqiu.flow.mapper.ProcDeployDefMapper;
import com.flyqiu.flow.mapper.ProcRuActivityMapper;
import com.flyqiu.flow.mapper.RuProcInstMapper;
import com.flyqiu.flow.mapper.RuUserTaskInstMapper;
import com.flyqiu.flow.bpmn.element.BaseProcElement;
 ;
import com.flyqiu.flow.model.entity.ProcDeployDef;
import com.flyqiu.flow.model.entity.base.Activity;
import com.flyqiu.flow.model.entity.base.Task;
import com.flyqiu.flow.model.entity.base.Variable;
import com.flyqiu.flow.model.entity.ru.ProcRuActivity;
import com.flyqiu.flow.model.entity.ru.ProcRuInst;
import com.flyqiu.flow.model.entity.ru.ProcRuUserTask;
import com.flyqiu.flow.model.entity.ru.ProcRuVariable;


import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class FlowRunningServiceImpl implements FlowRunningService {

    private final RuProcInstMapper ruProcInstMapper;
    private final ProcDeployDefMapper procDefMapper;

    private final FlowVariableService flowVariableService;

    private final FlyQiuFlowGlobalContext procGlobalContext;
    private final FlyQiuTransactionManage transactionManage;

    private final FlowCommentService flowCommentService;
    private final IdentityLinkService identityLinkService;


    private final FlyQiuBeanManage procBeanManage;

    public FlowRunningServiceImpl(FlyQiuFlowGlobalContext procGlobalContext,
                                  FlowCommentService flowCommentService,
                                  IdentityLinkService identityLinkService,
                                  FlowVariableService flowVariableService) {
        this.procGlobalContext = procGlobalContext;
        procBeanManage = procGlobalContext.getBeanManage();
        transactionManage = procGlobalContext.getTransactionManage();

        ruProcInstMapper = procBeanManage.getMapper(RuProcInstMapper.class);
        procDefMapper = procBeanManage.getMapper(ProcDeployDefMapper.class);
        this.flowCommentService = flowCommentService;
        this.identityLinkService = identityLinkService;
        this.flowVariableService = flowVariableService;
    }


    @Override
    public ProcRuInst startByProcDefKey(String procDefKey, JSONObject procVariable, BaseFlyBusiness business) {
        Page<ProcDeployDef> page = new Page<>();
        page.setCurrent(0);
        page.setSize(1);
        LambdaQueryWrapper<ProcDeployDef> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProcDeployDef::getProcDeployDefKey, procDefKey);
        queryWrapper.eq(ProcDeployDef::getProcDefStatus, ProcDefStatus.ACTIVATE.getValue());
        queryWrapper.eq(StringTools.isNotBlank(FlyQiuUserTools.getTenantId()), ProcDeployDef::getTenantId, FlyQiuUserTools.getTenantId());
        queryWrapper.orderByDesc(ProcDeployDef::getProcDeployVersion);
        ProcDeployDef deployDef = procDefMapper.selectOne(queryWrapper);
        if (deployDef == null) {
            throw new RuntimeException("could not find procDef by procDefKey:" + procDefKey);
        }
        return start(deployDef, procVariable, business);
    }


    /**
     * 启动流程
     *
     * @param procDeployDefId 流程部署id
     * @param procVariable    流程变量
     * @param business        业务信息
     * @return {@link ProcRuInst} 流程实例信息
     */
    @Override
    public ProcRuInst start(String procDeployDefId, JSONObject procVariable, BaseFlyBusiness business) {
        ProcDeployDef procDeployDef = procDefMapper.selectById(procDeployDefId);
        return start(procDeployDef, procVariable, business);
    }

    private ProcRuInst start(ProcDeployDef deployDef, JSONObject procVariableMap, BaseFlyBusiness business) {
        if (deployDef == null || StringTools.isAllBlank(deployDef.getId())) {
            throw new RuntimeException("procDeployDefId cannot be null or empty");
        }
        if (deployDef.getProcDefStatus() == null) {
            throw new RuntimeException("This process definition  procDefStatus cannot be null or empty");
        }
        BpmnModelCache bpmnModelCache = procGlobalContext.getFlowModelSourceManage().getBpmModeler(deployDef);
        if (bpmnModelCache == null) {
            throw new RuntimeException("could not find procDef by procDeployDefId: " + deployDef.getId());
        }
        ProcDeployDef procDef = bpmnModelCache.deployDef();
        if (!ProcDefStatus.ACTIVATE.getValue().equals(procDef.getProcDefStatus())) {
            throw new RuntimeException("This process definition is not enabled by procDeployDefId:" + deployDef);
        }

        Map<String, Variable> mapVariable = FlyQiuCollectionTools.map(procVariableMap, (key, varVal) -> {
            String typeName = varVal.getClass().getName();
            ProcRuVariable variable = new ProcRuVariable();
            variable.setVarName(key);
            variable.setValType(typeName);
            variable.setActInstId("");
            variable.setActId("");
            variable.setVarValue(varVal.toString());
            return variable;
        }).stream().collect(Collectors.toMap(ProcRuVariable::getVarName, e -> e, (e1, e2) -> e1));
        FlyQiuUser flyQiuFlowAuthentication = FlyQiuUserTools.getAuthentication();
        if (flyQiuFlowAuthentication == null) {
            throw new RuntimeException("FlyQiuUserTools.getAuthentication() cannot be null or empty");
        }
        if (StringUtils.checkValNull(flyQiuFlowAuthentication.getId())) {
            throw new RuntimeException("FlyQiuUser.getId() cannot be null or empty");
        }
        DefaultProcInstContext procContext = new DefaultProcInstContext();
        procContext.setProcInst(null);
        procContext.setGlobalContext(procGlobalContext);
        procContext.setFlyQiuBpmModeler(bpmnModelCache.bpmModeler());
        procContext.setProcVariables(new VariableScope(procContext.getGlobalContext().getFlowVarSerializeManage(), mapVariable));
        procContext.setCurProcessModel(bpmnModelCache.getProcess());
        procContext.setProcDeployDef(procDef);
        procContext.setBaseFlyBusiness(business);
        ProcessHandler processHandler = new ProcessHandler();
        return transactionManage.apply(() -> processHandler.start(procContext, procDef.getProcDeployDefKey()));

    }


    /**
     * 分页查询流程实例
     *
     * @param page
     * @param wrapper
     * @return
     */
    @Override
    public Page<ProcRuInst> pageProcInst(Page<ProcRuInst> page, Wrapper<ProcRuInst> wrapper) {
        return ruProcInstMapper.selectPage(page, wrapper);
    }

    /**
     * 流程查询
     *
     * @param wrapper
     * @return
     */
    @Override
    public List<ProcRuInst> listProcInst(Wrapper<ProcRuInst> wrapper) {
        return ruProcInstMapper.selectList(wrapper);
    }

    @Override
    public ProcInstContext getProcRuntimeContext(String procInstId) {
        ProcRuInst procRuInst = getProcInstByInstId(procInstId);
        return getProRuntimeContext(procRuInst);
    }

    private ProcInstContext getProRuntimeContext(ProcRuInst procRuInst) {
        if (procRuInst == null) {
            throw new RuntimeException("procInstId cannot be null or empty");
        }
        BpmnModelCache bpmnModelCache = procGlobalContext.getFlowModelSourceManage().getBpmModeler(procRuInst.getProcDeployDefId());
        ProcessModel process = bpmnModelCache.getProcess(procRuInst.getProcDeployDefKey());
        DefaultProcInstContext procContext = new DefaultProcInstContext();
        procContext.setProcInst(procRuInst);
        procContext.setGlobalContext(procGlobalContext);
        procContext.setFlyQiuBpmModeler(bpmnModelCache.bpmModeler());
        procContext.setCurProcessModel(process);
        procContext.setProcDeployDef(bpmnModelCache.deployDef());
        List<Variable> procRuVariables = flowVariableService.listRuVariable(procRuInst.getId()).stream().map(e -> (Variable) e).toList();
        VariableScope variableScope = new VariableScope(procGlobalContext.getFlowVarSerializeManage(), procRuVariables);
        procContext.setProcVariables(variableScope);
        return procContext;
    }

    /**
     * 流程实例查询
     *
     * @param wrapper
     * @return
     */
    @Override
    public ProcRuInst selectOneProcInst(Wrapper<ProcRuInst> wrapper) {
        return ruProcInstMapper.selectOne(wrapper);
    }

    /**
     * 流程实例查询
     *
     * @param wrapper
     * @return
     */
    @Override
    public long countProcInst(Wrapper<ProcRuInst> wrapper) {
        return ruProcInstMapper.selectCount(wrapper);
    }

    /**
     * @param procInstId 流程实例id
     * @param params     其他扩展参数
     * @param reason     删除原因
     */
    @Override
    public void deleteProcInst(String procInstId, JSONObject params, String reason) {
        ProcRuInst procRuInst = ruProcInstMapper.selectById(procInstId);
        if (procRuInst == null) {
            return;
        }
        transactionManage.apply(() -> {
            procGlobalContext.getFlowEventManage().triggerDeleteBeforeEvent(ProcInstEvent.class, procRuInst, params, reason);
            deleteSubProcInstByParentInstId(procInstId, params, reason);
            ruProcInstMapper.deleteById(procInstId);
            deleteTaskByProcInstId(procInstId, params, reason);
            deleteActivityByProcInstId(procInstId, params, reason);
            flowVariableService.deleteRuVariable(procInstId);
            procGlobalContext.getFlowEventManage().triggerDeleteAfterEvent(ProcInstEvent.class, procRuInst, params, reason);
        });

    }

    @Override
    public Page<ProcRuActivity> pageActivity(Page<ProcRuActivity> page, LambdaQueryWrapper<ProcRuActivity> queryWrapper) {
        ProcRuActivityMapper mapper = procBeanManage.getMapper(ProcRuActivityMapper.class);
        return mapper.selectPage(page, queryWrapper);
    }

    @Override
    public List<ProcRuActivity> listActivity(LambdaQueryWrapper<ProcRuActivity> queryWrapper) {
        return procBeanManage.getMapper(ProcRuActivityMapper.class).selectList(queryWrapper);
    }

    @Override
    public long countActivity(LambdaQueryWrapper<ProcRuActivity> queryWrapper) {
        return procBeanManage.getMapper(ProcRuActivityMapper.class).selectCount(queryWrapper);
    }

    @Override
    public ProcRuActivity getActivity(String activityId) {
        return procBeanManage.getMapper(ProcRuActivityMapper.class).selectById(activityId);
    }


    @Override
    public List<ProcRuActivity> activityMoveTo(String activityId, String targetDefKey) {
        ProcRuActivity activity = getActivity(activityId);
        if (activity == null) {
            throw new RuntimeException("Could not find ProcRuActivity instance with activity id: " + activityId);
        }
        if (activity.getDefKey().equals(targetDefKey)) {
            throw new RuntimeException("The current activity definition key " + targetDefKey + " is the same as the target activity definition key");
        }
        ProcRuInst procRuInst = getProcInstByInstId(activity.getProcInstId());
        if (procRuInst == null) {
            throw new RuntimeException("Could not find ProcRuInst instance with procInstId id: " + activity.getProcInstId());
        }
        if (!ProcInstStatus.ACTIVATE.getValue().equals(procRuInst.getStatus())) {
            throw new RuntimeException("The current ProcRuInst instance state is not active");
        }
        BpmnModelCache bpmModeler = procGlobalContext.getFlowModelSourceManage().getBpmModeler(procRuInst.getProcDeployDefId());
        BaseProcElement eneElement = bpmModeler.bpmModeler().getFlowElement(targetDefKey);
        if (eneElement == null) {
            throw new RuntimeException("Could not find element defined as: " + targetDefKey + " in the process definition");
        }
        BaseProcElement startElement = bpmModeler.bpmModeler().getFlowElement(activity.getDefKey());
        if (startElement == null) {
            throw new RuntimeException("Could not find element defined as: " + activity.getDefKey() + " in the process definition");
        }

        DefaultProcMove procMove = new DefaultProcMove();
        procMove.setStartElement(startElement);
        procMove.setEndElement(eneElement);
        procMove.setFilter(new DefaultBpmnPathFilter());
        return activityMoveTo(procRuInst, activity, bpmModeler, procMove);
    }

    @Override
    public List<ProcRuActivity> activityMoveTo(String activityId, String targetDefKey, IProIcActivityMove move) {
        ProcRuActivity activity = getActivity(activityId);
        ProcRuInst procRuInst = getProcInstByInstId(activity.getProcInstId());
        if (procRuInst == null) {
            throw new RuntimeException("Could not find ProcRuInst instance with procInstId id: " + activity.getProcInstId());
        }
        if (!ProcInstStatus.ACTIVATE.getValue().equals(procRuInst.getStatus())) {
            throw new RuntimeException("The current ProcRuInst instance state is not active");
        }
        BpmnModelCache bpmModeler = procGlobalContext.getFlowModelSourceManage().getBpmModeler(procRuInst.getProcDeployDefId());
        BaseProcElement eneElement = bpmModeler.bpmModeler().getFlowElement(targetDefKey);
        if (eneElement == null) {
            throw new RuntimeException("Could not find element defined as: " + targetDefKey + " in the process definition");
        }
        BaseProcElement startElement = bpmModeler.bpmModeler().getFlowElement(activity.getDefKey());
        if (startElement == null) {
            throw new RuntimeException("Could not find element defined as: " + activity.getDefKey() + " in the process definition");
        }
        return activityMoveTo(procRuInst, activity, bpmModeler, move);
    }


    private List<ProcRuActivity> activityMoveTo(ProcRuInst procRuInst, ProcRuActivity activity, BpmnModelCache bpmModeler, IProIcActivityMove procMove) {
        if (procMove == null) {
            throw new RuntimeException("procMove cannot be null");
        }
        if (activity == null) {
            throw new RuntimeException("activity cannot be null");
        }

        if (procRuInst == null) {
            throw new RuntimeException("Could not find ProcRuInst instance with procInstId id: " + activity.getProcInstId());
        }
        if (!ProcInstStatus.ACTIVATE.getValue().equals(procRuInst.getStatus())) {
            throw new RuntimeException("The current ProcRuInst instance state is not active");
        }

        FlowRuVariableService flowRuVariableService = procBeanManage.getBean(FlowRuVariableService.class);
        ProcRuInst ruInst = getProcInstByInstId(activity.getProcInstId());
        List<Variable> procVariables = new ArrayList<>(flowRuVariableService.getProcVariables(ruInst.getId()));

        VariableScope variableScope = new VariableScope(procGlobalContext.getFlowVarSerializeManage(), procVariables);
        DefaultProcInstContext procContext = new DefaultProcInstContext();
        procContext.setProcVariables(variableScope);
        procContext.setProcInst(ruInst);
        procContext.setGlobalContext(procGlobalContext);
        procContext.setFlyQiuBpmModeler(bpmModeler.bpmModeler());
        procContext.setCurProcessModel(bpmModeler.getProcess(ruInst.getProcDeployDefKey()));
        procContext.setProcDeployDef(bpmModeler.deployDef());
        ElementCompleteHandler actInst = procGlobalContext.getFlowElementManage().createCompleteInst(activity.getActType());
        procMove.setMoveReadyListener(() -> {
            transactionManage.apply(() -> {
                actInst.complete(procContext, activity);
            });
        });
        procMove.move();
        LambdaQueryWrapper<ProcRuActivity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProcRuActivity::getProcInstId, activity.getProcInstId());
        queryWrapper.eq(Activity::getDefKey, procMove.getEndDefKey());
        queryWrapper.eq(StringTools.isNotBlank(activity.getTenantId()), ProcRuActivity::getTenantId, activity.getTenantId());
        queryWrapper.ge(ProcRuActivity::getCreateTime, activity.getCreateTime());
        queryWrapper.orderByDesc(ProcRuActivity::getCreateTime);
        List<ProcRuActivity> procRuActivities = listActivity(queryWrapper);
        if (FlyQiuCollectionTools.isEmpty(procRuActivities)) {
            return null;
        }
        return procRuActivities;


    }


    private void deleteTaskByProcInstId(String procInstId, JSONObject params, String reason) {
        RuUserTaskInstMapper taskInstMapper = procBeanManage.getMapper(RuUserTaskInstMapper.class);
        LambdaQueryWrapper<ProcRuUserTask> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Task::getProcInstId, procInstId);
        List<ProcRuUserTask> procRuUserTasks = taskInstMapper.selectList(queryWrapper);
        FlyQiuCollectionTools.iterator(procRuUserTasks, procRuUserTask -> {
            procGlobalContext.getFlowEventManage().triggerDeleteBeforeEvent(UserTaskEvent.class, procRuUserTask, params, reason);
            procGlobalContext.getFlowEventManage().triggerDeleteAfterEvent(UserTaskEvent.class, procRuUserTask, params, reason);
        });
        taskInstMapper.delete(queryWrapper);
    }

    private void deleteActivityByProcInstId(String procInstId, JSONObject params, String reason) {
        ProcRuActivityMapper activityMapper = procBeanManage.getMapper(ProcRuActivityMapper.class);
        LambdaQueryWrapper<ProcRuActivity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProcRuActivity::getProcInstId, procInstId);
        List<ProcRuActivity> procRuActivities = activityMapper.selectList(queryWrapper);
        FlyQiuCollectionTools.iterator(procRuActivities, procRuActivity -> {
            procGlobalContext.getFlowEventManage().triggerDeleteBeforeEvent(ActInstEvent.class, procRuActivity, params, reason);
            identityLinkService.deleteRuIdentityLink(procRuActivity.getProcInstId(), procRuActivity.getId());
            flowCommentService.deleteComment(procRuActivity.getProcInstId(), procRuActivity.getId());
            flowVariableService.deleteRuVariable(procRuActivity.getProcInstId(), procRuActivity.getId());
            procGlobalContext.getFlowEventManage().triggerDeleteAfterEvent(ActInstEvent.class, procRuActivity, params, reason);
        });
        activityMapper.delete(queryWrapper);
    }

    private void deleteSubProcInstByParentInstId(String procInstId, JSONObject params, String reason) {
        LambdaQueryWrapper<ProcRuInst> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProcRuInst::getParentId, procInstId);
        List<ProcRuInst> procRuInsts = ruProcInstMapper.selectList(queryWrapper);
        if (FlyQiuCollectionTools.isEmpty(procRuInsts)) {
            return;
        }
        for (ProcRuInst subInst : procRuInsts) {
            deleteSubProcInstByParentInstId(subInst.getId(), params, reason);
            procGlobalContext.getFlowEventManage().triggerDeleteBeforeEvent(ProcInstEvent.class, subInst, params, reason);
            deleteActivityByProcInstId(subInst.getId(), params, reason);
            deleteTaskByProcInstId(subInst.getId(), params, reason);
            flowVariableService.deleteRuVariable(subInst.getId());
            procGlobalContext.getFlowEventManage().triggerDeleteBeforeEvent(ProcInstEvent.class, subInst, params, reason);
        }
    }
}
